using System;
using System.Text;
using System.Runtime.InteropServices;

namespace Tamir.IPLib
{
	/// <summary>
	/// Summary description for SharpPcap.
	/// </summary>
	public class SharpPcap
	{
		/// <summary>A delegate for Packet Arrival events</summary>
		public delegate void PacketArrivalEvent(object sender, Packets.Packet packet);

		/// <summary>
		/// A delegate for delivering network statistics
		/// </summary>
		public delegate void PcapStatisticsEvent(object sender, PcapStatistics statistics);

		/// <summary>
		/// A delegate fornotifying of a capture stopped event
		/// </summary>
		public delegate void PcapCaptureStoppedEvent(object sender, bool error);

		/// <summary>Represents the infinite number for packet captures </summary>
		public const int INFINITE = -1;

		/// <summary>A string value that prefixes avery pcap device name </summary>
		internal const string PCAP_NAME_PREFIX = @"\Device\NPF_";

		[DllImport("wpcap.dll", CharSet=CharSet.Auto)]
		private extern static int pcap_findalldevs(ref IntPtr /* pcap_if_t** */ alldevs, StringBuilder /* char* */ errbuf);

		/// <summary>Create a list of network devices that can be opened with pcap_open().</summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_findalldevs_ex (string /*char **/source, IntPtr /*pcap_rmtauth **/auth, ref IntPtr /*pcap_if_t ** */alldevs, StringBuilder /*char * */errbuf);
		
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static void	pcap_freealldevs(IntPtr /* pcap_if_t * */ alldevs);

		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr /* pcap_t* */ pcap_open_live(string dev, int packetLen, short mode, short timeout, StringBuilder errbuf);

		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr /* pcap_t* */ pcap_open_offline( string/*const char* */ fname, StringBuilder/* char* */ errbuf ); 

		/// <summary>Open a file to write packets. </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr /*pcap_dumper_t * */ pcap_dump_open (IntPtr /*pcap_t * */adaptHandle, string /*const char * */fname);
		
		/// <summary>
		///  Save a packet to disk.  
		/// </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static void  pcap_dump (IntPtr /*u_char * */user, IntPtr /*const struct pcap_pkthdr * */h, IntPtr /*const u_char * */sp) ;
		
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr /* pcap_t* */ pcap_open(string dev, int packetLen, short mode, short timeout,IntPtr auth, StringBuilder errbuf);

		/// <summary> close the files associated with p and deallocates resources.</summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static void  pcap_close (IntPtr /*pcap_t **/adaptHandle) ;			 

		/// <summary>
		/// To avoid callback, this returns one packet at a time
		/// </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_next_ex(IntPtr /* pcap_t* */ adaptHandle, ref IntPtr /* **pkt_header */ header , ref IntPtr  data);

		/// <summary>
		/// Send a raw packet.<br>
		/// This function allows to send a raw packet to the network. 
		/// The MAC CRC doesn't need to be included, because it is transparently calculated
		///  and added by the network interface driver.	/// </summary>
		/// <param name="adaptHandle">the interface that will be used to send the packet</param>
		/// <param name="data">contains the data of the packet to send (including the various protocol headers)</param>
		/// <param name="size">the dimension of the buffer pointed by data</param>
		/// <returns>0 if the packet is succesfully sent, -1 otherwise.</returns>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_sendpacket(IntPtr /* pcap_t* */ adaptHandle, IntPtr  data, int size);

		/// <summary>
		/// Allocate a send queue. 
		/// </summary>
		/// <param name="memsize">The size of the queue</param>
		/// <returns>A pointer to the allocated buffer</returns>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr /*pcap_send_queue * */pcap_sendqueue_alloc(int memsize) ;

		/// <summary>
		/// Destroy a send queue. 
		/// </summary>
		/// <param name="queue">A pointer to the queue start address</queue>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static void pcap_sendqueue_destroy(IntPtr /* pcap_send_queue * */queue) ;


		/// <summary>
		/// Add a packet to a send queue. 
		/// </summary>
		/// <param name="queue">A pointer to a queue</param>
		/// <param name="header">The pcap header of the packet to send</param>
		/// <param name="data">The packet data</param>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_sendqueue_queue(IntPtr /* pcap_send_queue * */queue, IntPtr /* **pkt_header */ header , IntPtr  data);
			 

		/// <summary>
		/// Send a queue of raw packets to the network. 
		/// </summary>
		/// <param name="p"></param>
		/// <param name="queue"></param>
		/// <param name="sync">determines if the send operation must be synchronized: 
		/// if it is non-zero, the packets are sent respecting the timestamps, 
		/// otherwise they are sent as fast as possible</param>
		/// <returns>The amount of bytes actually sent. 
		/// If it is smaller than the size parameter, an error occurred 
		/// during the send. The error can be caused by a driver/adapter 
		/// problem or by an inconsistent/bogus send queue.</returns>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_sendqueue_transmit(IntPtr/*pcap_t * */p, IntPtr /* pcap_send_queue * */queue, int sync);

		/// <summary>
		/// Compile a packet filter, converting an high level filtering expression (see Filtering expression syntax) in a program that can be interpreted by the kernel-level filtering engine. 
		/// </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_compile (IntPtr /* pcap_t* */ adaptHandle, IntPtr /*bpf_program **/fp, string /*char * */str, int optimize, UInt32 netmask);

		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int  pcap_setfilter (IntPtr /* pcap_t* */ adaptHandle, IntPtr /*bpf_program **/fp);

		/// <summary>
		/// return the error text pertaining to the last pcap library error.
		/// </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr pcap_geterr (IntPtr /*pcap_t * */ adaptHandle);

		/// <summary>Returns a pointer to a string giving information about the version of the libpcap library being used; note that it contains more information than just a version number. </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static string /*const char **/  pcap_lib_version ();
		
		/// <summary>return the standard I/O stream of the 'savefile' opened by pcap_dump_open().</summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static IntPtr /*FILE **/  pcap_dump_file (IntPtr /*pcap_dumper_t **/p);
		
		/// <summary>Flushes the output buffer to the ``savefile,'' so that any packets 
		/// written with pcap_dump() but not yet written to the ``savefile'' will be written. 
		/// -1 is returned on error, 0 on success. </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_dump_flush (IntPtr /*pcap_dumper_t **/p);
			
		/// <summary>Closes a savefile. </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static void  pcap_dump_close (IntPtr /*pcap_dumper_t **/p);
				
		/// <summary> Return the link layer of an adapter. </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_datalink(IntPtr /* pcap_t* */ adaptHandle);

		/// <summary>
		/// Set the working mode of the interface p to mode. 
		///	Valid values for mode are MODE_CAPT (default capture mode) 
		///	and MODE_STAT (statistical mode). See the tutorial 
		///	"\ref wpcap_tut9" for details about statistical mode. 
		/// </summary>
		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		internal extern static int pcap_setmode  ( IntPtr/* pcap_t * */ p, int  mode );

		/* interface is loopback */
		internal const uint		PCAP_IF_LOOPBACK				= 0x00000001;
		private  const int		MAX_ADAPTER_NAME_LENGTH			= 256;		
		private  const int		MAX_ADAPTER_DESCRIPTION_LENGTH	= 128;
		internal const int		MAX_PACKET_SIZE					= 65536;
		internal const int		PCAP_ERRBUF_SIZE				= 256;
		internal const int		MODE_CAPT						=	0;
		internal const int		MODE_STAT						=	1;
		internal const string	PCAP_SRC_IF_STRING				= "rpcap://";

		#region Callback Implementation ( Not Working from some reason, Bug?)

		[DllImport("wpcap.dll", CharSet=CharSet.Ansi)]
		private extern static int pcap_loop(IntPtr /* pcap_t* */ adaptHandle, short count, PcapHandler callback, IntPtr ptr);

		/// <summary>
		/// From some reason the Callback inplementation doesn't work.
		/// It fires for one time and then throws null pointer exception
		/// </summary>
		private delegate void PcapHandler(IntPtr param, IntPtr /* pcap_pkthdr* */ header, IntPtr pkt_data);

		private void MyPacketHandler(IntPtr param, IntPtr /* pcap_pkthdr* */ header, IntPtr pkt_data)
		{
			DateTime tm;
			
			if (header != IntPtr.Zero)
			{
				PCAP_PKTHDR PktInfo = (PCAP_PKTHDR)Marshal.PtrToStructure( header, typeof(PCAP_PKTHDR) );
				/* convert the timestamp to readable format */
				tm = new DateTime( (PktInfo.tv_usec) );				
			
				Console.WriteLine("{0}, len: {1}", tm.ToShortTimeString(), PktInfo.len);
			}
		}

		#endregion Callback Implementation (Not Working)

		#region Unmanaged Structs Implementation

		/// <summary>
		/// Item in a list of interfaces.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			internal struct PCAP_IF 
		{
			public IntPtr /* pcap_if* */	Next;			
			public string					Name;			/* name to hand to "pcap_open_live()" */				
			public string					Description;	/* textual description of interface, or NULL */
			public IntPtr /*pcap_addr * */	Addresses;
			public uint						Flags;			/* PCAP_IF_ interface flags */
		};

		/// <summary>
		/// Representation of an interface address.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			internal struct PCAP_ADDR 
		{
			public IntPtr /* pcap_addr* */	Next;
			public IntPtr /* sockaddr * */	Addr;		/* address */
			public IntPtr /* sockaddr * */  Netmask;	/* netmask for that address */
			public IntPtr /* sockaddr * */	Broadaddr;	/* broadcast address for that address */
			public IntPtr /* sockaddr * */	Dstaddr;	/* P2P destination address for that address */
		};

		/// <summary>
		/// Structure used by kernel to store most addresses.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			internal struct SOCKADDR 
		{
			public int			sa_family;       /* address family */
			[MarshalAs(UnmanagedType.ByValArray, SizeConst=14)]
			public byte[]		sa_data;         /* up to 14 bytes of direct address */
		};


		/// <summary>
		/// Each packet in the dump file is prepended with this generic header.
		/// This gets around the problem of different headers for different
		/// packet interfaces.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			public struct PCAP_PKTHDR 
		{											//timestamp
			public int		tv_sec;				///< seconds
			public int		tv_usec;			///< microseconds
			public int		caplen;			/* length of portion present */
			public int		len;			/* length this packet (off wire) */
		};

		/// <summary>
		/// Packet data bytes
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			internal struct PCAP_PKTDATA
		{	
			[MarshalAs(UnmanagedType.ByValArray, SizeConst=MAX_PACKET_SIZE)]						
			public byte[]		bytes;
		};

		/// <summary>
		/// A BPF pseudo-assembly program for packet filtering
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			internal struct bpf_program 
		{
			public uint bf_len;                
			public IntPtr /* bpf_insn **/ bf_insns;  
		};

		/// <summary>
		/// A queue of raw packets that will be sent to the network with pcap_sendqueue_transmit()
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
			internal struct pcap_send_queue 
		{
			public uint maxlen;   
            public uint len;   
			public IntPtr /* char **/ ptrBuff;  
		};


		#endregion Unmanaged Structs Implementation

		public static string Version
		{
			get{return pcap_lib_version();}
		}

		/// <summary>
		/// Returns all pcap network devices available on this machine.
		/// </summary>
		public static PcapDeviceList GetAllDevices()
		{
			IntPtr ptrDevs = IntPtr.Zero; // pointer to a PCAP_IF struct
			IntPtr next = IntPtr.Zero;    // pointer to a PCAP_IF struct
			PCAP_IF pcap_if;
			StringBuilder errbuf = new StringBuilder( 256 ); //will hold errors
			PcapDeviceList deviceList = new PcapDeviceList();

			/* Retrieve the device list */
			int res = pcap_findalldevs(ref ptrDevs, errbuf);
			if (res == -1)
			{
				string err = "Error in WinPcap.GetAllDevices(): " + errbuf;
				throw new Exception( err );
			}
			else
			{	// Go through device structs and add to list
				next = ptrDevs;
				while (next != IntPtr.Zero)
				{
					pcap_if = (PCAP_IF)Marshal.PtrToStructure(next, typeof(PCAP_IF)); //Marshal memory pointer into a struct
					if(NetworkDevice.IsNetworkDevice( pcap_if.Name ))
					{
						try
						{
							deviceList.Add( new NetworkDevice(pcap_if) );
						}
						catch
						{
							deviceList.Add( new PcapDevice(pcap_if) );
						}
					}
					else
					{
						deviceList.Add( new PcapDevice(pcap_if) );
					}
					
					next = pcap_if.Next;
				}
			}
			pcap_freealldevs( ptrDevs );  // free buffers
			return deviceList;
		}

		/// <summary>
		/// Returns a PCAP_IF struct representing a pcap network device
		/// </summary>
		/// <param name="pcapName">The name of a device.<br>
		/// Can be either in pcap device format or windows network
		/// device format</param>
		/// <returns></returns>
		internal static PCAP_IF GetPcapDeviceStruct(string pcapName)
		{
			if( !pcapName.StartsWith( PCAP_NAME_PREFIX ) )
			{
				pcapName = PCAP_NAME_PREFIX+pcapName;
			}
			IntPtr ptrDevs = IntPtr.Zero; // pointer to a PCAP_IF struct
			IntPtr next = IntPtr.Zero;    // pointer to a PCAP_IF struct
			PCAP_IF pcap_if;
			StringBuilder errbuf = new StringBuilder( 256 ); //will hold errors

			/* Retrieve the device list */
			int res = pcap_findalldevs(ref ptrDevs, errbuf);
			if (res == -1)
			{
				string err = "Error in SharpPcap.GetAllDevices(): " + errbuf;
				throw new Exception( err );
			}
			else
			{	// Go through device structs and search for 'pcapName'
				next = ptrDevs;
				while (next != IntPtr.Zero)
				{
					pcap_if = (PCAP_IF)Marshal.PtrToStructure(next, typeof(PCAP_IF)); //Marshal memory pointer into a struct
					if(pcap_if.Name==pcapName)
					{
						pcap_freealldevs( ptrDevs );  // free buffers
						return pcap_if;
					}
					next = pcap_if.Next;
				}
			}
			pcap_freealldevs( ptrDevs );  // free buffers
			throw new Exception("Device not found: "+pcapName);
		}

		internal static PCAP_ADDR GetPcap_Addr(IntPtr pcap_addrPtr)
		{
			//A sockaddr struct
			PCAP_ADDR pcap_addr;
			//Marshal memory pointer into a struct
			pcap_addr = (PCAP_ADDR)Marshal.PtrToStructure(pcap_addrPtr, typeof(PCAP_ADDR));
			return pcap_addr;		
		}

		internal static int GetPcapAddress(IntPtr sockaddrPtr)
		{
			//A sockaddr struct
			SOCKADDR sockaddr;
			//Marshal memory pointer into a struct
			sockaddr = (SOCKADDR)Marshal.PtrToStructure(sockaddrPtr, typeof(SOCKADDR));
			return BitConverter.ToInt32( sockaddr.sa_data, 0);
		}
		
		public static PcapOfflineDevice GetPcapOfflineDevice(string pcapFileName)
		{
			return new PcapOfflineDevice( pcapFileName );
		}

		public static PcapDevice GetPcapDevice( string pcapDeviceName )
		{
			return new PcapDevice( GetPcapDeviceStruct( pcapDeviceName ) );
		}
	}
}
